package com.kaithan.gateway.util;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;


/**
 * 类描述：使用 Caffeine 实现本地缓存工具类
 *
 * @author ZhuYin
 * @since 2025年02月16日
 */
public class CaffeineUtil {

    /**
     * 方法说明：键值自增
     *
     * @param key        键
     * @param increment  自增跨度，正整数
     * @return long
     * @author ZhuYin
     * @since 2025年02月16日
     */
    public static long incrBy(String key, int increment) {
        // 如果键不存在，初始化为 0 执行递增操作并返回结果
        return ((AtomicLong) Objects.requireNonNull(Objects.requireNonNull(CAFFEINE.get(key, k -> new CacheObject<>(new AtomicLong(0), DEFAULT_EXPIRE))).data)).addAndGet(increment);
    }

    private static class CacheObject<T> {
        T data;
        // 存储绝对过期时间（纳秒）
        long expireAt;

        public CacheObject(T data, long second) {
            this.data = data;
            // 计算绝对过期时间
            this.expireAt = System.nanoTime() + TimeUnit.SECONDS.toNanos(second);
        }

        /**
         * 检查是否已过期
         */
        public boolean isExpired() {
            return System.nanoTime() > expireAt;
        }
    }

    /**
     * 不设置过期时长，默认时间为5分钟
     */
    public static final long DEFAULT_EXPIRE = TimeUnit.MINUTES.toMillis(5);

    public static final Cache<String, CacheObject<?>> CAFFEINE = Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(1024)
            .build();

    /**
     * 方法说明：设置缓存，带过期时间（秒）
     *
     * @param key       键
     * @param value     值
     * @param expire    过期时间，单位：毫秒
     * @author ZhuYin
     * @since 2025年02月16日
     */
    public static <T> void set(String key, T value, long expire) {
        if (expire < 0) {
            throw new IllegalArgumentException("Expire time must be non-negative");
        }
        CacheObject<T> cacheObject = new CacheObject<>(value, expire / 1000);
        CAFFEINE.put(key, cacheObject);
    }

    /**
     * 设置缓存，默认过期时间
     */
    public static <T> void set(String key, T value) {
        set(key, value, DEFAULT_EXPIRE);
    }

    /**
     * 方法说明：给key设置过期时间
     *
     * @param key       键
     * @param expire    过期时间，单位：秒
     * @author ZhuYin
     * @since 2025年02月16日
     */
    public static void setExpire(String key, long expire) {
        if (expire < 0) {
            throw new IllegalArgumentException("Expire time must be non-negative");
        }
        Object value = get(key);
        if (value != null) {
            set(key, value, expire);
        }
    }

    /**
     * 获取缓存值
     */
    public static Object get(String key) {
        CacheObject<?> cacheObject = CAFFEINE.getIfPresent(key);
        if (cacheObject == null || cacheObject.isExpired()) {
            // 如果缓存已过期，删除并返回 null
            if (cacheObject != null && cacheObject.isExpired()) {
                delete(key);
            }
            return null;
        }
        return cacheObject.data;
    }

    /**
     * 获取缓存值
     */
    public static <T> T get(String key, Class<T> clazz) {
        CacheObject<?> cacheObject = CAFFEINE.getIfPresent(key);
        if (cacheObject == null || cacheObject.isExpired()) {
            // 如果缓存已过期，删除并返回 null
            if (cacheObject != null && cacheObject.isExpired()) {
                delete(key);
            }
            return null;
        }
        return (T) cacheObject.data;
    }

    /**
     * 删除单个缓存
     */
    public static void delete(String key) {
        CAFFEINE.invalidate(key);
    }

    /**
     * 批量删除缓存
     */
    public static void delete(Collection<String> keys) {
        CAFFEINE.invalidateAll(keys);
    }

    /**
     * 方法说明：判断缓存中是否存在某个key
     *
     * @param key   键
     * @return boolean true-存在 false-不存在
     * @author ZhuYin
     * @since 2025年02月16日
     */
    public static boolean containsKey(String key) {
        return get(key) != null;
    }

    /**
     * 测试用例
     */
    public static void main(String[] args) throws InterruptedException {
        // 设置缓存，key 过期时间为 3 秒
        CaffeineUtil.set("key", "张三", 3);
        // 立即获取缓存值
        String s1 = CaffeineUtil.get("key", String.class);
        System.out.println("初始获取：" + s1);

        // 循环获取缓存值，每秒一次
        for (int i = 0; i < 200; i++) {
            String s = CaffeineUtil.get("key", String.class);
            System.out.println((i + 1) + " 秒后获取：" + s);
            // 每次等待 1 秒
            Thread.sleep(1000);
        }
    }
}